package com.soyea.service;

import com.google.common.base.Function;
import com.google.common.collect.*;
import com.soyea.dao.SysAclMapper;
import com.soyea.dao.SysAclModuleMapper;
import com.soyea.dao.SysDeptMapper;
import com.soyea.dto.AclDto;
import com.soyea.dto.AclModuleLevelDto;
import com.soyea.dto.DeptLevelDTO;
import com.soyea.model.SysAcl;
import com.soyea.model.SysAclModule;
import com.soyea.model.SysDept;
import com.soyea.util.LevelUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 系统树结构
 * Created by skh on 2018/1/3
 */
@Service
@Transactional
public class SysTreeService {

	@Autowired
	private SysDeptMapper sysDeptMapper;

	@Autowired
	private SysAclModuleMapper sysAclModuleMapper;

	@Resource
	private SysCoreService sysCoreService;
	@Resource
	private SysAclMapper sysAclMapper;

	/**
	 * 获取指定用户的树结构的权限列表
	 * 注意点:这里的做法是只显示用户拥有的权限,不显示所有权限,
	 * 好处是:用户只知道自己拥有哪些权限,不知道还有哪些权限,更加安全
	 *
	 * 思路:
	 * 使用AclDto,新增两个属性: checked是否要默认选中和hasAcl是否有权限操作,默认都为false
	 * checked该属性作用是前端通过此属性判断是否要打钩
	 * hasAcl该属性作用是权限拦截器通过该属性判断是否有权限
	 *
	 * 先获取当前用户的权限列表
	 * 遍历权限列表,将每个权限转化成权限DTO,并给两个属性设置成true
	 * 将权限列表转化为权限DTO列表
	 * 将权限DTO列表转化成树结构
	 * 最后返回填充好两个属性的树形权限模块列表
	 *
	 * @param userId
	 * @return
	 */
	public List<AclModuleLevelDto> userAclTree(int userId) {
		//获取当前用户的权限列表
		List<SysAcl> userAclList = sysCoreService.getUserAclList(userId);
		List<AclDto> aclDtoList = Lists.newArrayList();
		for (SysAcl acl : userAclList) {
			//遍历权限列表,设置每个权限DTO属性.
			AclDto dto = AclDto.adapt(acl);
			dto.setHasAcl(true);
			dto.setChecked(true);
			aclDtoList.add(dto);
		}
		//将权限DTO列表转为树结构
		return aclListToTree(aclDtoList);
	}

	/**
	 * 获取指定角色的树结构的权限列表
	 *
	 * 注意点:这里的做法,显示所有的权限,如果角色有该权限,则在当前权限前打钩.
	 * 好处是:可以知道该角色拥有哪些权限,不拥有哪些权限.
	 *
	 * 思路
	 * 获取当前用户已分配的权限列表
	 * 获取当前角色分配的权限列表
	 * 遍历两个列表,只获取其中的id值,转化成两个idSet集合
	 * 获取所有权限列表
	 * 遍历所有权限的列表,将每个权限转化成权限DTO,通过set集合的contain方法判断用户和角色是否拥有该权限,修改权限DTO属性值
	 * 将权限DTO列表转化为权限模块DTO列表
	 * 最后返回填充好两个属性的树形权限模块列表
	 *
	 * @param roleId
	 * @return
	 */
	public List<AclModuleLevelDto> roleTree(int roleId) {
		// 1、当前用户已分配的权限点
		List<SysAcl> userAclList = sysCoreService.getCurrentUserAclList();
		// 2、当前角色分配的权限点
		List<SysAcl> roleAclList = sysCoreService.getRoleAclList(roleId);


		//当前用户已分配的权限点Id列表
		Set<Integer> userAclIdSet = userAclList.stream().map(sysAcl -> sysAcl.getId()).collect(Collectors.toSet());
		//当前角色已分配的权限点ID列表
		Set<Integer> roleAclIdSet = roleAclList.stream().map(sysAcl -> sysAcl.getId()).collect(Collectors.toSet());

		//获取所有权限列表
		List<SysAcl> allAclList = sysAclMapper.getAll();
		List<AclDto> aclDtoList = Lists.newArrayList();
		for (SysAcl acl : allAclList) {
			//遍历所有权限,判断当前用户是否有权限操作该权限,判断当前角色是否选中该权限
			AclDto dto = AclDto.adapt(acl);
			if (userAclIdSet.contains(acl.getId())) {
				dto.setHasAcl(true);
			}
			if (roleAclIdSet.contains(acl.getId())) {
				dto.setChecked(true);
			}
			aclDtoList.add(dto);
		}
		return aclListToTree(aclDtoList);
	}

	/**
	 * 返回树形权限模块AclModuleLevelDto列表
	 * AclModuleLevelDto多了两个属性,一个该模块的子模块列表,一个是该模块下的权限列表
	 *
	 * 思路
	 * 获取所有的权限模块的树列表,还需要填充对应的权限列表
	 * 为了获取每个权限模块下对应的权限模块列表
	 * 使用multimap将权限DTO列表根据moduleId分层,Map<moduleId,List<aclDTO>>
	 * 权限的状态是有效的才加入map
	 * 使用递归算法 将 相应权限模块下的权限列表 绑定到 相应权限模块下
	 * 最后返回填充好两个属性的树形权限模块列表
	 *
	 * @param aclDtoList
	 * @return
	 */
	public List<AclModuleLevelDto> aclListToTree(List<AclDto> aclDtoList) {
		if (CollectionUtils.isEmpty(aclDtoList)) {
			return Lists.newArrayList();
		}

		//获取只赋值了子模块列表的根权限模块DTO列表
		//只填充了子权限模块列表属性,还需要填充对应的权限列表
		List<AclModuleLevelDto> aclModuleLevelList = aclModuleTree();

		//将所有权限List根据moduleId分类, key:acl_moduleId -> value:List<aclDTO>
		Multimap<Integer, AclDto> moduleIdAclMap = ArrayListMultimap.create();
		for(AclDto acl : aclDtoList) {
			if (acl.getStatus() == 1) {
				//当权限有效才加入Map
				moduleIdAclMap.put(acl.getAclModuleId(), acl);
			}
		}

		//将 相应权限模块下的权限列表 绑定到 相应权限模块下
		bindAclsWithOrder(aclModuleLevelList, moduleIdAclMap);
		return aclModuleLevelList;
	}

	/**
	 * 使用递归将 相应权限模块下的权限列表 绑定到 相应权限模块下
	 *
	 * 思路
	 * 遍历根权限模块列表
	 * 获取相应权限模块下的权限列表
	 * 如果权限列表不为空,将权限列表排序,赋值给AclModuleLevelDto
	 * 递归,获取子权限模块列表.
	 * 退出条件:当子权限模块列表为空时
	 *
	 * @param aclModuleLevelList 需要处理的权限模块树列表,一开始是根权限模块列表
	 * @param moduleIdAclMap 根据moduleId分类好的所有权限列表
	 */
	public void bindAclsWithOrder(List<AclModuleLevelDto> aclModuleLevelList, Multimap<Integer, AclDto> moduleIdAclMap) {
		if (CollectionUtils.isEmpty(aclModuleLevelList)) { //退出条件.
			return;
		}
		//遍历根权限模块列表
		for (AclModuleLevelDto dto : aclModuleLevelList) {

			List<AclDto> aclDtoList = (List<AclDto>) moduleIdAclMap.get(dto.getId()); //获取相应权限模块下的权限列表

			if (CollectionUtils.isNotEmpty(aclDtoList)) {
				//排序
				Collections.sort(aclDtoList, aclSeqComparator);
				dto.setAclList(aclDtoList);
			}
			bindAclsWithOrder(dto.getAclModuleList(), moduleIdAclMap); //放在外面是因为,有可能某个权限模块下没有权限,但是它还有子权限模块
		}
	}

	/**
	 * 获取权限模块树列表
	 *
	 * 思路:类似部门树列表
	 *
	 * 获取所有权限模块的列表
	 * 将权限模块列表转化为权限模块DTO列表
	 * 将权限模块DTO列表转化为树列表
	 * 返回只赋值了子模块列表的根权限模块DTO列表
	 *
	 * @return
	 */
	public List<AclModuleLevelDto> aclModuleTree() {
		List<SysAclModule> aclModuleList = sysAclModuleMapper.getAllAclModule();
		//将List<SysAclModule>->List<AclModuleLevelDto>
		List<AclModuleLevelDto> dtoList = Lists.newArrayList();
		for (SysAclModule aclModule : aclModuleList) {
			dtoList.add(AclModuleLevelDto.adapt(aclModule));
		}
		return aclModuleListToTree(dtoList);
	}

	/**
	 * 将权限模块DTO列表转化为树列表
	 *
	 * 思路:类似部门树列表
	 * 使用MultiMap将权限模块列表按照level来区分 Multimap 等价于 Map<String, List<Object>>
	 * 遍历权限模块dto列表,将权限模块dto放入MultiMap中
	 * 根据权限模块的level,先获取根权限模块dto列表
	 * 使用比较器,将跟权限模块列表按照seq从小到大排序
	 * 使用递归算法生成树,给rootList里的每个权限模块设置子权限模块列表属性
	 * 最终返回已经赋好值的根权限模块列表
	 *
	 * @param dtoList
	 * @return
	 */
	public List<AclModuleLevelDto> aclModuleListToTree(List<AclModuleLevelDto> dtoList) {
		if (CollectionUtils.isEmpty(dtoList)) {
			return Lists.newArrayList();
		}
		// level -> [aclmodule1, aclmodule2, ...] Map<String, List<Object>>
		Multimap<String, AclModuleLevelDto> levelAclModuleMap = ArrayListMultimap.create();
		List<AclModuleLevelDto> rootList = Lists.newArrayList();

		for (AclModuleLevelDto dto : dtoList) {
			levelAclModuleMap.put(dto.getLevel(), dto);
			if (LevelUtil.ROOT.equals(dto.getLevel())) {
				rootList.add(dto);
			}
		}

		//排序
		Collections.sort(rootList, aclModuleSeqComparator);
		transformAclModuleTree(rootList, LevelUtil.ROOT, levelAclModuleMap);
		return rootList;
	}

	/**
	 * 递归权限模块树
	 * 给每个权限模块DTO设置子权限模块列表
	 *
	 * 思路:类似部门树列表
	 * 遍历需要处理的权限模块列表,最开始是根权限模块列表
	 * 获取到每个权限管理,通过每个权限管理的level+当前处理的level,计算出下一个需要处理的level
	 * 根据nextLevel,从levelDeptMap中取出该level下的权限管理列表,即子权限管理列表
	 * 判断子权限管理列表是否为空
	 * 如果不为空,则需要为当前权限管理设置子权限管理列表,设置前,需要将子权限管理列表排序
	 * 递归该算法,因为子权限管理下还可能有子权限管理.
	 * 递归退出条件:通过子权限管理列表是否为空
	 * 该算法中,会变的参数只有deptLevelList和level
	 *
	 * @param dtoList 需要处理的数据
	 * @param level 需要处理的level
	 * @param levelAclModuleMap 根据level值,封装好的Map
	 */
	public void transformAclModuleTree(List<AclModuleLevelDto> dtoList, String level, Multimap<String, AclModuleLevelDto> levelAclModuleMap) {
		for (int i = 0; i < dtoList.size(); i++) {
			AclModuleLevelDto dto = dtoList.get(i);
			String nextLevel = LevelUtil.calculateLevel(level, dto.getId());
			List<AclModuleLevelDto> tempList = (List<AclModuleLevelDto>) levelAclModuleMap.get(nextLevel);
			if (CollectionUtils.isNotEmpty(tempList)) { //递归退出的条件.
				Collections.sort(tempList, aclModuleSeqComparator);
				dto.setAclModuleList(tempList); //设置子模块列表
				transformAclModuleTree(tempList, nextLevel, levelAclModuleMap);
			}
		}
	}

	/**
	 * 获取部门树
	 *
	 * 部门树的特点:
	 * 根据层级,将部门列表分层
	 * 如果该部门有子部门列表,则为它设置子部门列表的属性
	 *
	 * 思路:
	 * 使用DeptLevelDTO,增加一个子部门列表属性,为它设置该属性
	 * 将deptList转换成deptDtoList
	 * 将deptDtoList转换成树形结构
	 *
	 * @return
	 */
	public List<DeptLevelDTO> deptTree() {
		//查询所有部门
		List<SysDept> deptList = sysDeptMapper.getAllDept();

		//将deptList转换成deptDtoList
		List<DeptLevelDTO> dtoList = Lists.newArrayList();
		for (SysDept dept : deptList) {
			DeptLevelDTO dto = DeptLevelDTO.adapt(dept);
			dtoList.add(dto);
		}
		return deptListToTree(dtoList);
	}

	/**
	 * 将部门列表转换为树结构
	 *
	 * 思路:
	 * 使用MultiMap将部门列表按照level来区分 Multimap 等价于 Map<String, List<Object>>
	 * 遍历部门dto列表,将部门dto放入MultiMap中
	 * 根据部门的level,先获取根部门dto列表
	 * 使用比较器,将跟部门列表按照seq从小到大排序
	 * 使用递归算法生成树,给rootList里的每个部门设置子部门列表属性
	 * 最终返回已经赋好值的根部门列表
	 *
	 * @param deptLevelList
	 * @return
	 */
	private List<DeptLevelDTO> deptListToTree(List<DeptLevelDTO> deptLevelList) {
		if (CollectionUtils.isEmpty(deptLevelList)) {
			return Lists.newArrayList();
		}
		// level -> [DeptLevelDTO1, DeptLevelDTO2, ...]  Multimap 等价于 Map<String, List<Object>>
		// 该map用来实现,将部门按照level来区分.
		Multimap<String, DeptLevelDTO> levelDeptMap = ArrayListMultimap.create();
		List<DeptLevelDTO> rootList = Lists.newArrayList(); //根部门列表

		//封装数据 获取levelDeptMap和rootList
		for (DeptLevelDTO dto : deptLevelList) {
			levelDeptMap.put(dto.getLevel(), dto);
			if (LevelUtil.ROOT.equals(dto.getLevel())) {
				rootList.add(dto);
			}
		}

		// 将跟部门列表按照seq从小到大排序
		Collections.sort(rootList, deptSeqComparator);

		// 递归生成树
		transformDeptTree(rootList, LevelUtil.ROOT, levelDeptMap);
		return rootList;
	}



	/**
	 * 递归部门树 算法
	 * 通过level值来处理不同层级的部门
	 * 通过计算出来的下级level值,在Multimap中获取子部门列表,然后赋值该属性
	 *
	 * 思路:
	 * 遍历需要处理的部门列表,最开始是根部门列表
	 * 获取到每个部门,通过每个部门的level+当前处理的level,计算出下一个需要处理的level
	 * 根据nextLevel,从levelDeptMap中取出该level下的部门列表,即子部门列表
	 * 判断子部门列表是否为空
	 * 如果不为空,则需要为当前部门设置子部门列表,设置前,需要将子部门列表排序
	 * 递归该算法,因为子部门下还可能有子部门.
	 * 递归退出条件:通过子部门列表是否为空
	 * 该算法中,会变的参数只有deptLevelList和level
	 *
	 * @param deptLevelList 需要处理的数据
	 * @param level 当前需要处理的level值
	 * @param levelDeptMap 根据level值,封装好的Map
	 */
	private void transformDeptTree(List<DeptLevelDTO> deptLevelList, String level, Multimap<String, DeptLevelDTO> levelDeptMap) {
		//一层一层处理 先处理根部门列表
		for (int i = 0; i < deptLevelList.size(); i++) {
			// 遍历该层的每个元素
			DeptLevelDTO deptLevelDTO = deptLevelList.get(i);
			// 获取下一层的level
			String nextLevel = LevelUtil.calculateLevel(level, deptLevelDTO.getId());
			// 处理下一层 获取子部门列表
			List<DeptLevelDTO> tempDeptList = (List<DeptLevelDTO>) levelDeptMap.get(nextLevel);
			if (CollectionUtils.isNotEmpty(tempDeptList)) { //递归退出条件:通过子部门列表是否为空
				// 排序
				Collections.sort(tempDeptList, deptSeqComparator);
				// 设置子部门列表
				deptLevelDTO.setDeptList(tempDeptList);
				// 进入到下一层处理
				transformDeptTree(tempDeptList, nextLevel, levelDeptMap);
			}
		}
	}

	/**
	 * 部门比较器,根据seq排序
	 */
	private Comparator<DeptLevelDTO> deptSeqComparator = new Comparator<DeptLevelDTO>() {
		public int compare(DeptLevelDTO o1, DeptLevelDTO o2) {
			return o1.getSeq() - o2.getSeq();
		}
	};

	/**
	 * 权限模块比较器
	 */
	public Comparator<AclModuleLevelDto> aclModuleSeqComparator = new Comparator<AclModuleLevelDto>() {
		public int compare(AclModuleLevelDto o1, AclModuleLevelDto o2) {
			return o1.getSeq() - o2.getSeq();
		}
	};

	/**
	 * 权限比较器
	 */
	public Comparator<AclDto> aclSeqComparator = new Comparator<AclDto>() {
		public int compare(AclDto o1, AclDto o2) {
			return o1.getSeq() - o2.getSeq();
		}
	};

}
